<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
	<meta http-equiv="pragma" content="no-cache" />
    <meta name="author" content="Jayli" />
	<meta name="keywords" content="淘宝UED 前端工程师 拔赤" />	
	<meta name="description" content="淘宝前端工程师拔赤，关注尖端前端技术，关注yui" />
	<meta http-equiv="imagetoolbar" content="no" />
	<meta name="title" content="拔赤的文章列表" />
    <title>js观察者模式以及基于YUI中的实现</title>
    <link href="http://jayli.github.com/blog/atom.xml" rel="alternate" title="setImpl" type="application/atom+xml" />
    <link rel="stylesheet" href="http://jayli.github.com/blog/media/css/style.css">
    <link rel="stylesheet" href="http://jayli.github.com/blog/media/css/github.css">
	<style>
	</style>
    <script type="text/javascript" src="http://jayli.github.com/blog/media/js/highlight.pack.js"></script>
    <script type="text/javascript">
      hljs.initHighlightingOnLoad();
    </script>
	<!--[if lte IE 8]>
	<script src="http://a.tbcdn.cn/apps/lottery/00023/index-v3/js/html5.js"></script>
	<![endif]-->
	<meta name="baidu-tc-verification" content="afb6c9df553ef3493d9ee65263df0d55" />
	<script src="http://siteapp.baidu.com/static/webappservice/uaredirect.js" type="text/javascript"></script><script type="text/javascript">uaredirect("http://m.zoojs.org", "http://jayli.github.com/blog");</script>
  </head>
  <body>
    <div id="container">
      <div id="main" role="main">
        <header>
        <h1>js观察者模式以及基于YUI中的实现</h1>
        </header>
        <nav>
        <span><a title="home page" class="" href="/blog/index.html">home</a></span>
        <span><a title="tags" class="" href="/blog/tags.html">tags</a></span>
        <span><a title="about" class="" href="/blog/about.html">about me</a></span>
        <span><a title="flickr" class="" href="http://www.flickr.com/photos/lijing00333">flickr</a></span>
        <span><a title="blogroll" class="" href="/blog/links.html">links</a></span>
        <span><a title="projects" class="" href="/blog/projects.html">projects</a></span>
        <span><a title="subscribe me" class="" href="/blog/atom.xml">feed</a></span>
        </nav>
        <article class="content">
        <section class="post">
<p>观察者模式是一种典型的设计模式，不论基于什么语言研讲的设计模式都包含它。概括讲，观察者模式就是“当A事件发生的时候自动执行B事件”，这是我们所期望的结果，但这句话是如此之概括以至于很多关键细节被忽略掉了，更进一步讲，观察者模式是对“事件联动性”行为的模拟，这种模拟是如此之抽象，以至于我们在类似js这种事件满天飞的语言中忽视“事件”的细节。从这个角度讲，的确不利于我们理解js中的事件。</p>

<p>js依赖于浏览器，浏览器在启动的时候会为我们做很多事情，因此我们才可以通过一个简单的a.onclick = function(e){…}来实现当点击事件发生时要做的事情。但从设计模式的角度讲，这只是浏览器对click事件实现了观察者模式，即当用鼠标左键单击某个元素并致使元素状态改变时发生“点击”事件。之所以如此罗嗦的使用“用鼠标点击左键单击某个元素并致使元素状态改变”这种晦涩的语言来描述这，是因为这样才能比较准确完整的描述一个“事件”所应具备的特征，从而方便我们能模仿这个过程来做自定义事件。而‘单击左键’或‘元素状态改变’这些逻辑是被浏览器”捕捉”并”处理”，我们是看不到这些细节的。也就是说，浏览器将它认为常用的事件的定义、注册、监听行为都捆绑在浏览器运行时之中，只有事件需要绑定的句柄需要在js中给出。但如果我想使用浏览器没有默认提供的事件，比如元素尺寸改变、或者左键三击事件，又该如何做呢?</p>

<p>先让我们分析一下简单的点击事件：
通常，绑定点击事件我们使用：</p>

<pre><code>el.onclick = function(e){
    //dosth here
}
</code></pre>

<p>el 表示要点击的对象，我们称之为宿主，onclick表示事件类型，与此同时，浏览器在无时无刻的监听元素上发生的事情，并判断el状态是否发生改变，以此判断是否发生了某类事件，比如点击事件，进而开始执行用户为该元素的onclick句柄所指向的函数。由此看出，是否触发用户给 el.onclick绑定的函数是由浏览器判断完成的，浏览器承担了甄别事件类型和宿主并触发绑定函数的角色。而所谓“甄别事件类型和宿主”则是对一个 “事件”的抽象理解，自定义事件则需要实现这个抽象的“事件”逻辑。</p>

<p>以上，可以得出，一个事件的发生依赖与2个重要属性，宿主和状态，宿主用来判断事件发生地，状态用来判断是否fire用户绑定的事件。所有的事件都包含这两者。只是在浏览器原生事件的宿主和状态只被浏览器捕捉，对开发者不可见。</p>

<p>有了抽象的事件逻辑，有了事件的状态和宿主，还需要”事件注册”，只有“注册过”的事件才可以被识别和使用，或者说被大规模使用而不用重写代码，事件逻辑是固定的，不固定的只是宿主和事件发生后fire的函数。对于宿主的“状态”的监听，有两种方式，每个固定的时间间隔进行判断，或者手动给出需要判断状态的位置。出于性能考虑，常用第二种方法。</p>

<p>因此，一个完整的事件的发生是由“事件”、“宿主”、“宿主状态”、“事件发生”（也就是事件发生的位置）、“判断宿主状态”（也就是判断是否 fire绑定的函数）、和“事件绑定函数”这几部分组成。任何一个完整的事件都是由这些内容组成，包括浏览器原生事件和自定义事件。</p>

<p>在 YUI2.x中既是按照如上思路模拟了事件的全过程，并封装为customEvent（自定义）事件类，可以很方便的实现自定义事件，YUI2.x中的这种对观察者模式精炼的实践在复杂的web application中应用很广而且比较高效。在YUI3中，每类自定义事件都被抽象出来，形成“事件工厂”，事件工厂生成事件对象，事件对象注册事件类型，并绑定fire的函数，这种更深层次的抽象对自定义事件的管理是大有裨益的。最牛X的，YUI3的自定义事件居然可以冒泡，这些都是后话，先来实践下YUI3下的自定义事件，我们来作一个鼠标三击事件。</p>

<p>首先要注意的是，鼠标点击事件在ie和firefox里的表现是有区别的。两次快速的连续点击在ie中被当作双击事件，而在firefox中触发了两次单击事件和一次双击事件。因此这里需要针对ie作简单hack。</p>

<p>js代码:</p>

<pre><code>YUI().use(‘dump’,'node’,function(Y){

    //发布者工厂
    var tripleClickFactory = function(id,interval){
        this.el = Y.get(id);
        this.status = false;
        this.trp = [];//三次点击的时间
        this.interval = interval||100;//毫秒
    };

    //包装发布者
    Y.augment(tripleClickFactory, Y.Event.Target);

    //tripleClick事件对象
    var tripleClick = new tripleClickFactory(‘#iid’,800);

    //绑定函数
    tripleClick.subscribe(‘tpClick’, function(a){
        //a,时间间隔数组
        alert(‘三击事件,三次点击的两个间隔分别为:’+a[0]+’和’+a[1]+’毫秒’);
    });

    //事件
    var tripleClickEvent = function(e){
        //var el = e.target;
        tripleClick.trp.push((new Date()).getTime());
        if(tripleClick.trp.length 3){
            var a = [];
            for(var i = 1;i&lt;= 3;i++){
                a[i-1] = tripleClick.trp[ i ];
            }
            delete tripleClick.trp;
            tripleClick.trp = a;
        }
        var s1 = tripleClick.trp[2] – tripleClick.trp[1];
        var s2 = tripleClick.trp[1] – tripleClick.trp[0];

        if(Number(s1)&lt;=tripleClick.interval &amp;&amp; 
                Number(s2) &lt;=tripleClick.interval){
            tripleClick.fire('tpClick',[s1,s2]);
            tripleClick.trp = [];
        }
    };

    ///////////////runtime///////////////////

    Y.on('click',function(e){
        tripleClickEvent(e);
    },'#iid');

    if(Y.UA.ie != 0){//hack for ie
        Y.on('dblclick',function(e){
            tripleClickEvent(e);
        },'#iid');
    }

});
</code></pre>

</section>
<section class="meta">

<!--span class="tags">
  tagged by 
  
</span-->

<span class="time">
  posted at <time datetime="2008-10-10">2008-10-10</time>
</span>
</section>

<section class="comment">
<div id="disqus_thread"></div>
<script type="text/javascript">
    /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
    var disqus_shortname = 'jayliblog'; // required: replace example with your forum shortname
	var disqus_identifier = 'urn:uuid:6ec83167-c315-41de-aca2-aa89a7b355e7';

    /* * * DON'T EDIT BELOW THIS LINE * * */
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</section>


        </article>
      </div>
    </div> <!--! end of #container -->
  </body>
</html>
